﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using System.Reflection;
using System.IO;
using System.Data;
using KingOfCSharp.iHunter;
using KingOfCSharp.iHunter.Database;
using KingOfCSharp.iHunter.Database.MasterDataSetTableAdapters;

namespace KingOfCSharp.iHunter
{
    /// <summary>
    /// The plugin manager
    /// </summary>
    public partial class PluginManager : Component
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="PluginManager"/> class.
        /// </summary>
        public PluginManager()
        {
            InitializeComponent();
        }

        private void InitFields()
        {
            pluginDict = new Dictionary<Guid, List<IPlugin>>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PluginManager"/> class.
        /// </summary>
        /// <param name="container">The container.</param>
        public PluginManager(IContainer container)
        {
            container.Add(this);

            InitializeComponent();
        }



        LocalClient localClient;
        MasterDataSet masterDataSet;
        Dictionary<Guid, List<IPlugin>> pluginDict;
        



  
        /// <summary>
        /// Inits the plugin manager.
        /// </summary>
        /// <param name="client">The client.</param>
        public void Initialize(LocalClient client)
        {
            localClient = client;
            masterDataSet = localClient.MasterDataSet;
            InitFields();
            LoadPlugins();
        }



        /// <summary>
        /// Load all installed plugins
        /// </summary>
        public void LoadPlugins()
        {
            if (masterDataSet.Plugins.Count == 0)
            {
                //First run, load system plugins
                string systemPluginsDir = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Plugins");
                if (!Directory.Exists(systemPluginsDir))
                    return;
                string[] systemPluginFiles = Directory.GetFiles(systemPluginsDir);
                foreach (string file in systemPluginFiles)
                {
                    Assembly assembly;
                    Guid assemblyGuid;
                    string name;
                    string description;

                    try
                    {
                        //Load dll
                        byte[] bytes = File.ReadAllBytes(file);
                        assembly = Assembly.Load(bytes);
                        //Get guid attribute of dll
                        assemblyGuid = this.GetAssemblyGuid(assembly);
                        //Get title attribute of dll
                        name = this.GetAssemblyName(assembly);
                        //Get description attribute of dll
                        description = this.GetAssemblyDescription(assembly);
                    }
                    catch (Exception e)
                    {
                        this.OnPluginError(null, "Load dll failed");
                        continue;
                    }
                    pluginDict.Add(assemblyGuid, new List<IPlugin>());

                    PluginClient pluginClient = new PluginClient(localClient, assemblyGuid);
                    IPlugin[] plugins = this.LoadPluginsFromFile(file);
                    if (plugins.Length == 0)
                    {
                        this.OnPluginError(null, "Not a valid plugin");
                        continue;
                    }

                    MasterDataSet.PluginsRow prow = masterDataSet.Plugins.NewPluginsRow();
                    prow.Guid = assemblyGuid;
                    prow.Name = name;
                    prow.PluginPath = systemPluginsDir;
                    prow.BinaryPath = Path.GetFileName(file);
                    prow.DataStorePath = null;
                    prow.BinaryType = 1;
                    prow.Description = description;
                    prow.Settings = null;
                    prow.State = (int)PluginState.Enabled;
                    masterDataSet.Plugins.AddPluginsRow(prow);

                    foreach (IPlugin plugin in plugins)
                    {
                        //Init Plugin
                        this.InitPlugin(plugin,assemblyGuid,pluginClient);
                    }


                }
            }
            else
            {
                MasterDataSet.PluginsRow row = null;
                for (int i=0; i<masterDataSet.Plugins.Count; ++i)
                {
                    row = masterDataSet.Plugins[i];
                    if (row.State == (int)PluginState.Enabled)
                    {
                        string file = Path.Combine(row.PluginPath, row.BinaryPath);
                        PluginClient pluginClient = new PluginClient(localClient, row.Guid);
                        IPlugin[] plugins = this.LoadPluginsFromFile(file);
                        if (plugins.Length == 0)
                        {
                            this.OnPluginError(null, "Load plugin failed");
                            continue;
                        }
                        foreach (IPlugin plugin in plugins)
                        {
                            this.InitPlugin(plugin,row.Guid,pluginClient);
                        }
                    }
                    else if (row.State == (int)PluginState.Uninstalling)
                    {
                        string pluginPath = row.PluginPath;

                        try
                        {
                            if (Directory.Exists(pluginPath))
                                Directory.Delete(pluginPath, true);
                            row.Delete();
                        }
                        catch (Exception e)
                        {
                            row.RejectChanges();
                        }
                    }
                }
            }
            TableAdapterManager.Instance.UpdateAll(this.masterDataSet);
        }


        /// <summary>
        /// Install a plugin from a dll file
        /// </summary>
        /// <param name="srcPluginFile">path to specify a dll file</param>
        /// <param name="isRoot">whether has root authority</param>
        public void InstallPlugin(string srcPluginFile, bool isRoot)
        {
            if (!File.Exists(srcPluginFile))
            {
                this.OnPluginError(null, String.Format("File {0} Not Found", srcPluginFile));
                return;
            }

            Assembly assembly;
            string name;
            string description;
            Guid assemblyGuid;

            try
            {
                //Load dll
                byte[] bytes = File.ReadAllBytes(srcPluginFile);
                //assembly = Assembly.ReflectionOnlyLoad(bytes);
                assembly = Assembly.Load(bytes);
                //Get guid attribute of dll
                assemblyGuid = this.GetAssemblyGuid(assembly);
                //Get title attribute of dll
                name = this.GetAssemblyName(assembly);
                //Get description attribute of dll
                description = this.GetAssemblyDescription(assembly);
            }
            catch (Exception e)
            {
                this.OnPluginError(null, "Load dll failed");
                return;
            }

            if (this.masterDataSet.Plugins.Select(string.Format("Guid='{0}'", assemblyGuid)).Length != 0)
                return;

            if (pluginDict.ContainsKey(assemblyGuid))
                return;
            else
                pluginDict.Add(assemblyGuid, new List<IPlugin>());

            //Create plugin path
            string installDir;
            if (isRoot)
            {
                installDir = Path.Combine(
                    Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Plugins"),
                    assemblyGuid.ToString());
            }
            else
            {
                installDir = Path.Combine(
                    Path.Combine((String)AppDomain.CurrentDomain.GetData("DataDirectory"), "Plugins"),
                    assemblyGuid.ToString());
            }

            Directory.CreateDirectory(installDir);
           
            //Copy dll file into plugin path
            string binaryFilePath = Path.Combine(installDir,
                Path.GetFileName(srcPluginFile));
            File.Copy(srcPluginFile, binaryFilePath,true);

            //Create  plugins
            PluginClient pluginClient = new PluginClient(localClient, assemblyGuid);
            IPlugin[] plugins = this.LoadPluginsFromFile(binaryFilePath);

            if (plugins.Length == 0)
            {
                this.OnPluginError(null, "Not a valid plugin");
                return;
            }

            //Updata DataSet and Data Source
            MasterDataSet.PluginsRow prow = masterDataSet.Plugins.NewPluginsRow();
            prow.Guid = assemblyGuid;
            prow.Name = name;
            prow.PluginPath = installDir;
            prow.BinaryPath = Path.GetFileName(binaryFilePath);
            prow.DataStorePath = null;
            prow.BinaryType = 1;
            prow.Description = description;
            prow.Settings = null;
            prow.State = (int)PluginState.Enabled;
            masterDataSet.Plugins.AddPluginsRow(prow);

            //Init plugin
            foreach (IPlugin plugin in plugins)
            {
                //Init Plugin
                this.InitPlugin(plugin,assemblyGuid,pluginClient);
            }


        }


        /// <summary>
        /// Install a plugin from a dll file
        /// </summary>
        /// <param name="srcPluginFile">path to specify a dll file</param>
        public void InstallPlugin(string srcPluginFile)
        {
            this.InstallPlugin(srcPluginFile, false);
        }

        /// <summary>
        /// Determines whether the specified plugin is installed system wide.
        /// </summary>
        /// <param name="guid">The pluginGUID.</param>
        /// <returns>True if the plugin is system wide, otherwise false</returns>
        /// <remarks>
        /// A plugin is system wide means it is installed in the program installing directory
        /// uninstalling these plugins need administrator privilege
        /// </remarks>
        /// 
        public bool IsSystemPlugin(Guid guid)
        {
            MasterDataSet.PluginsRow row = masterDataSet.Plugins.FindByGuid(guid);
            if (row == null || row.RowState == DataRowState.Deleted)
            {
                return false;
            }
            if (row.PluginPath == Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Plugins"))
                return true;
            else
                return false;
        }

        private bool IsSystemPlugin(MasterDataSet.PluginsRow row)
        {
            if (row.PluginPath == Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Plugins"))
                return true;
            else
                return false;
        }

        /// <summary>
        /// Uninstalls the plugin.
        /// </summary>
        /// <param name="guid">The GUID.</param>
        /// <returns>True if uninstalled successfully, otherwise false</returns>
        public bool UninstallPlugin(Guid guid)
        {
            MasterDataSet.PluginsRow row = masterDataSet.Plugins.FindByGuid(guid);
            if (row == null || row.RowState == DataRowState.Deleted)
            {
                return false;
            }

            if (row.State == (int)PluginState.Uninstalling)
                return false;

            if (this.IsSystemPlugin(row))
                return false;

            if (this.pluginDict.ContainsKey(guid))
            {
                this.DisablePlugin(guid);

                row.State = (int)PluginState.Uninstalling;

                return true;
            }
            else
            {
                string pluginPath = row.PluginPath;

                try
                {
                    if (Directory.Exists(pluginPath))
                        Directory.Delete(pluginPath, true);
                    row.Delete();
                    TableAdapterManager.Instance.UpdateAll(this.masterDataSet);
                }
                catch (Exception e)
                {
                    row.RejectChanges();
                    return false;
                }
                return true;
            }
        }

        /// <summary>
        /// Enable a plugin
        /// </summary>
        /// <param name="guid">The GUID</param>
        public void EnablePlugin(Guid guid)
        {
            //If it is already enabled, return
            if (pluginDict.ContainsKey(guid))
            {
                return;
            }

            //Check whether is a valid plugin
            MasterDataSet.PluginsRow row = masterDataSet.Plugins.FindByGuid(guid);
            if (row == null || row.RowState == DataRowState.Deleted)
            {
                this.OnPluginError(null, "No Such Plugin");
                return;
            }

            if (row.State == (int)PluginState.Uninstalling)
                return;

            //Load dll
            string file = Path.Combine(row.PluginPath, row.BinaryPath);
            PluginClient pluginClient = new PluginClient(localClient, row.Guid);
            IPlugin[] plugins = this.LoadPluginsFromFile(file);
            pluginDict.Add(guid, new List<IPlugin>());
            if (plugins.Length == 0)
            {
                this.OnPluginError(null, "Enable plugin failed");
                return;
            }
            foreach (IPlugin plugin in plugins)
            {
                if (plugin == null)
                {
                    this.OnPluginError(null, "Load Plugin Failed");
                    continue;
                }
                //Init Plugin
                this.InitPlugin(plugin, row.Guid, pluginClient);
            }

            //Update DataSet and Data Source
            row.State = (int)PluginState.Enabled;
        }



        /// <summary>
        /// Disable a plugin
        /// </summary>
        /// <param name="guid">The Guid</param>
        public bool DisablePlugin(Guid guid)
        {
            if (!pluginDict.ContainsKey(guid))
                return false;


            //Check whether is a valid plugin
            //Error if data is not consistent in DataSet and Plugin Table
            MasterDataSet.PluginsRow pluginRow = masterDataSet.Plugins.FindByGuid(guid);
            if (pluginRow == null || pluginRow.RowState == DataRowState.Deleted)
            {
                return false;
            }

            if (pluginRow.State == (int)PluginState.Uninstalling)
                return false;


            //Dispose plugin                
            List<IPlugin> plugins = pluginDict[pluginRow.Guid];
            for (int i = 0; i < plugins.Count; ++i)
            {
                IPlugin plugin = plugins[i];
                plugin.Dispose();

                localClient.PluginClientsDictionary.Remove(plugin.Guid);
                if (plugin is IChannelProvider)
                {
                    localClient.ChannelProvidersDictionary.Remove(plugin.Guid);
                }
                if (plugin is IFilterProvider)
                {
                    localClient.FilterProvidersDictionary.Remove(plugin.Guid);
                }
                if (plugin is IViewerProvider)
                {
                    localClient.ViewerProvidersDictionary.Remove(plugin.Guid);
                }
                if (plugin is IEditorProvider)
                {
                    localClient.EditorProvidersDictionary.Remove(plugin.Guid);
                }
            }


            //Update PluginDict
            pluginDict.Remove(pluginRow.Guid);


            //Update DataSet and Data Source
            pluginRow.State = (int)PluginState.Disabled;
            return true;
        }



        /// <summary>
        /// Get information of all plugins
        /// </summary>
        /// <returns>A colletion of plugininfo</returns>
        public PluginInfo[] GetPluginInfos()
        {
            List<PluginInfo> pluginInfos = new List<PluginInfo>();

            foreach (MasterDataSet.PluginsRow row in masterDataSet.Plugins)
            {
                if (row.RowState != DataRowState.Deleted)
                {
                    PluginInfo info = new PluginInfo();
                    info.Guid = row.Guid;
                    info.Name = row.Name;
                    info.Description = row.Description;
                    info.Settings = row.Settings;
                    info.State = (PluginState)row.State;
                    pluginInfos.Add(info);
                }
            }

            return pluginInfos.ToArray();
        }

        public PluginInfo GetPluginInfo(Guid guid)
        {
            PluginInfo info = new PluginInfo();
            MasterDataSet.PluginsRow row = masterDataSet.Plugins.FindByGuid(guid);
            if (row == null || row.RowState == DataRowState.Deleted)
            {
                info.Guid = Guid.Empty;
            }
            else
            {
                info.Guid = row.Guid;
                info.Name = row.Name;
                info.Description = row.Description;
                info.Settings = row.Settings;
                info.State = (PluginState)row.State;
            }
            return info;
   
        }

        /// <summary>
        /// Check whether a plugin is installed
        /// </summary>
        /// <param name="guid">Guid of a plugin</param>
        /// <returns>Ture if installed, otherwise false</returns>
        private bool IsInstalled(Guid guid)
        {
            MasterDataSet.PluginsRow row = masterDataSet.Plugins.FindByGuid(guid);
            if (row == null || row.RowState == DataRowState.Deleted)
                return false;
            else
                return true;
        }


        /// <summary>
        /// Load plugin from file
        /// </summary>
        /// <param name="filename"></param>
        /// <returns>IPlugin, null if failed</returns>
        private IPlugin[] LoadPluginsFromFile(string filename)
        {
            List<IPlugin> plugins = new List<IPlugin>();
            try
            {
                Assembly assembly = Assembly.LoadFrom(filename);
                foreach (Module module in assembly.GetModules())
                {
                    foreach (Type type in module.GetTypes())
                    {
                        foreach (Type interfaceType in type.GetInterfaces())
                        {
                            if (interfaceType == typeof(IPlugin))
                            {
                                //TODO: Verify the plugin runs on the correct platform and archeteture, if PlatformAttributes or ArchetetureAttributes is given

                                foreach (ConstructorInfo cinfo in type.GetConstructors())
                                {
                                    if (cinfo.GetParameters().Length == 0 && !cinfo.IsStatic && cinfo.IsPublic)
                                    {
                                        IPlugin plugin = cinfo.Invoke(new object[0]) as IPlugin;
                                        if (plugin == null)
                                            continue;
                                        else
                                            plugins.Add(plugin);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch(Exception e)
            {
            }
            return plugins.ToArray();
        }

        private void InitPlugin(IPlugin plugin, Guid assemblyGuid, PluginClient pluginClient)
        {
            //Register plugin and pluginclient
            //PluginClient pluginClient = new PluginClient(localClient, plugin.Guid);
            if (plugin == null || plugin.Guid == Guid.Empty)
            {
                this.OnPluginError(plugin, "Init plugin failed");
                return;
            }
            
            if (plugin is IChannelProvider)
            {
                ChannelProviderWrapper wrapper = new ChannelProviderWrapper(plugin as IChannelProvider);
                InitWrapper(wrapper,assemblyGuid,pluginClient);
                localClient.ChannelProvidersDictionary.Add(wrapper.Guid, wrapper);
            }
            if (plugin is IFilterProvider)
            {
                FilterProviderWrapper wrapper = new FilterProviderWrapper(plugin as IFilterProvider);
                InitWrapper(wrapper,assemblyGuid,pluginClient);
                localClient.FilterProvidersDictionary.Add(wrapper.Guid, wrapper);
            }
            if (plugin is IViewerProvider)
            {
                ViewerProviderWrapper wrapper = new ViewerProviderWrapper(plugin as IViewerProvider);
                InitWrapper(wrapper,assemblyGuid,pluginClient);
                localClient.ViewerProvidersDictionary.Add(wrapper.Guid, wrapper);
            }
            if (plugin is IEditorProvider)
            {
                EditorProviderWrapper wrapper = new EditorProviderWrapper(plugin as IEditorProvider);
                InitWrapper(wrapper,assemblyGuid,pluginClient);
                localClient.EditorProvidersDictionary.Add(wrapper.Guid, wrapper);
            }
        }

        private void InitWrapper(PluginWrapper wrapper, Guid assemblyGuid, PluginClient pluginClient)
        {
            //Register plugin and pluginclient
            wrapper.Initialize(pluginClient);
            if (!this.pluginDict.ContainsKey(assemblyGuid))
                this.pluginDict.Add(assemblyGuid, new List<IPlugin>());
            this.pluginDict[assemblyGuid].Add(wrapper);
            wrapper.ErrorOccur += delegate(object sender, PluginErrorEventArgs args)
            {
                this.OnPluginError(wrapper.Plugin, args.ErrorMessage);
            };
        }


        private Guid GetAssemblyGuid(Assembly assembly)
        {
            //Get Guid of dll
            Guid guid = Guid.Empty;
            object[] assemblyObjects = assembly.GetCustomAttributes(typeof(System.Runtime.InteropServices.GuidAttribute), true);
            if (assemblyObjects.Length > 0)
                guid = new Guid(((System.Runtime.InteropServices.GuidAttribute)(assemblyObjects[0])).Value);
            else
                guid = Guid.NewGuid();
            return guid;
        }

        private string GetAssemblyName(Assembly assembly)
        {           
            //Get title attribute of dll
            string name;
            name = assembly.GetName().Name;
            object[] assemblyObjects = assembly.GetCustomAttributes(typeof(AssemblyTitleAttribute), true);
            if (assemblyObjects.Length > 0)
            {
                name = ((AssemblyTitleAttribute)assemblyObjects[0]).Title;
            }
            else
            {
            }
            return name;
        }

        private string GetAssemblyDescription(Assembly assembly)
        {
            //Get description attribute of dll
            string description = string.Empty;
            object[] assemblyObjects = assembly.GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false);
            if (assemblyObjects.Length > 0)
            {
                description = ((AssemblyDescriptionAttribute)assemblyObjects[0]).Description;
            }
            return description;
        }

        /// <summary>
        /// Handler of plugin exception
        /// </summary>
        public event PluginErrorEventHandler PluginInternalException;

        /// <summary>
        /// Handle plugin errors
        /// </summary>
        /// <param name="plugin">Plugin, might be null</param>
        /// <param name="errorMessage">Error message</param>
        protected void OnPluginError(IPlugin plugin, string errorMessage)
        {
            if (PluginInternalException != null)
                PluginInternalException.Invoke(this, new PluginErrorEventArgs(plugin, errorMessage));
        }
    }
}
